﻿using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Text;

namespace RW.Data.Objects
{
    public class ActionCost
    {
        public int Cost { get; set; }
        public LogicStatement Conditions { get; set; }
    }

    public class ActionResult
    {
        public LogicStatement ResultsCondition { get; set; }
        public LogicStatement Conditions { get; set; }
    }

    public class ActionRelease
    {
        public List<string> Result { get; set; }
        public LogicStatement Conditions { get; set; }
    }

    public class ActionStateResult
    {
        public State State { get; set; }
        public List<string> ChangedVariables { get; set; }

        public ActionStateResult(State state, List<string> changedVariables)
        {
            this.State = state;
            this.ChangedVariables = changedVariables;
        }
    }

    [DebuggerDisplay("{Name}")]
    public class Action
    {
        public string Name { get; private set; }
        public List<ActionCost> Costs { get; set; }
        public List<ActionResult> Result { get; set; }
        public List<ActionRelease> Released { get; set; }
        public List<LogicStatement> ImpossibleCondtions { get; set; }

        public Action(string name)
        {
            this.Name = name;

            this.Costs = new List<ActionCost>();
            this.Result = new List<ActionResult>();
            this.Released = new List<ActionRelease>();
            this.ImpossibleCondtions = new List<LogicStatement>();
        }

        public bool CheckIfPossible(State state)
        {
            bool result = true;

            foreach (LogicStatement impossibleCondtion in this.ImpossibleCondtions)
                result &= !impossibleCondtion.Execute(state);

            return result;
        }

        private Dictionary<string, bool> CreateStateFromNumber(int no, List<string> variables)
        {
            Dictionary<string, bool> result = new Dictionary<string, bool>();
            for (int i = 0; i < variables.Count; ++i)
                result.Add(variables[i], (no & (1 << i)) != 0);

            return result;
        }

        public List<ActionStateResult> GetResult(State state)
        {
            if (!this.CheckIfPossible(state))
                throw new Exception("Action is impossible from this state.");

            List<ActionStateResult> actionStateResult = new List<ActionStateResult>();
            List<string> releasedVariables = new List<string>();

            foreach (ActionRelease actionRelease in this.Released)
                if (actionRelease.Conditions.Execute(state))
                    releasedVariables = releasedVariables.Union(actionRelease.Result).ToList();

            for (int i = 0; i < Math.Pow(2, state.Keys.Count); i++)
            {
                State random = new State(this.CreateStateFromNumber(i, state.Keys.ToList()));

                List<string> changedVariables = state.Keys.Where(k => state[k] != random[k]).ToList();
                changedVariables = changedVariables.Union(releasedVariables).ToList();
                actionStateResult.Add(new ActionStateResult(random, changedVariables));
            }

            foreach (ActionResult actionResult in this.Result)
                if (actionResult.Conditions.Execute(state))
                    actionStateResult = actionStateResult.Except(actionStateResult.Where(a => !actionResult.ResultsCondition.Execute(a.State))).ToList();

            return actionStateResult;
        }

        public List<ActionStateResult> GetMinResult(State state)
        {
            List<ActionStateResult> results = this.GetResult(state);

            if (!results.Any())
                return new List<ActionStateResult>();

            int minResultValue = results.Min(r => r.ChangedVariables.Count);

            return results.Where(r => r.ChangedVariables.Count == minResultValue).ToList();
        }

        public int GetCost(State state)
        {
            int cost = 0;
            foreach (ActionCost actionCost in this.Costs)
            {
                if (actionCost.Conditions.Execute(state))
                    cost += actionCost.Cost;
            }

            return cost;
        }

        public override string ToString()
        {
            return Name;
        }
    }
}
